from rest_framework import serializers
from django.utils import timezone
from decimal import Decimal
from django_redis import get_redis_connection
from django.db import transaction

from goods.models import SKU
from .models import OrderInfo, OrderGoods


class CartGoodsSerializer(serializers.ModelSerializer):
    """购物车商品序列化器"""
    count = serializers.IntegerField(label='数量')

    class Meta:
        model = SKU
        fields = ('id', 'name', 'default_image_url', 'price', 'count')


class OrderSerializer(serializers.Serializer):
    """订单确认序列化器"""
    freight = serializers.DecimalField(label='运费', max_digits=10, decimal_places=2)
    skus = CartGoodsSerializer(many=True)


class CommitOrderSerializer(serializers.ModelSerializer):
    """用户订单提交序列化器"""
    class Meta:
        # 指定模型类
        model = OrderInfo
        # 指定序列化的字典
        fields = ('order_id', 'address', 'pay_method')
        # 指定进行序列化的字段(只读)
        read_only_fields = ('order_id',)
        # 指定进行反序列化的字段(只写)
        extra_kwargs = {
            'address': {
                'write_only': True,
            },
            'pay_method': {
                'write_only': True,
            }
        }

    def create(self, validated_data):
        """创建订单记录: 保存OrderInfo和OrderGoods信息"""
        # 获取当前保存订单时需要的信息
        # 获取当前登录用户的user对象
        user = self.context['request'].user

        # 构造订单ID:时间+定长的用户id
        # 时间:年月日时分秒
        # 用户id:定长九位
        order_id = timezone.now().strftime('%Y%m%d%H%M%S') + ("%09d" % user.id)

        # 验证后的address/pay_method
        address = validated_data.get('address')
        pay_method = validated_data.get('pay_method')

        # 明显的开启事物
        with transaction.atomic():
            # 在安全的地方创建保存点,操作数据库失败时回滚到此
            save_id = transaction.savepoint()

            try:
                # 保存订单基本信息 OrderInfo
                order = OrderInfo.objects.create(
                    order_id=order_id,
                    user=user,
                    address=address,
                    total_count=0,
                    total_amount=0,
                    freight=Decimal('10.00'),
                    pay_method=pay_method,
                    # 判断支付方式,如果为支付宝支付,则状态应为待支付
                    # 如果为货到付款，则状态应为待发货
                    status=OrderInfo.ORDER_STATUS_ENUM['UNPAID'] if pay_method == OrderInfo.PAY_METHODS_ENUM['ALIPAY'] else OrderInfo.ORDER_STATUS_ENUM['UNSEND']
                )

                # 从redis中读取被勾选的商品信息
                redis_conn = get_redis_connection('cart')
                # 获取购物车中所有数据
                # redis_cart: {b'1':b'10', b'2':b'20', b'3':b'30'}
                redis_cart = redis_conn.hgetall('cart_%s' % user.id)
                # redis_selected: {b'1', b'2'}
                redis_selected = redis_conn.smembers('selected_%s' % user.id)
                # 定义要支付的商品的信息字典
                # carts:{1:10, 2:20}
                carts = {}
                for sku_id in redis_selected:
                    carts[int(sku_id)] = int(redis_cart[sku_id])

                # 获取购物车中被勾选的sku_id
                sku_ids = carts.keys()
                # 遍历购物车中被勾选的商品信息
                for sku_id in sku_ids:
                    # 死循环的下单:当库存满足时,在下单时,库存没有同时被别人更改,下单成功,下单成功后,跳出死循环(break)
                    # 如果下单时库存被更改了,但是sku_count依然在被更改后的库存范围内,则继续下单(continue)
                    # 直到库存真正不满足条件时,才下单失败
                    while True:
                        # 获取sku对象
                        sku = SKU.objects.get(id=sku_id)

                        # 获取原始的库存和销量
                        origin_stock = sku.stock
                        origin_sales = sku.sales

                        sku_count = carts[sku_id]
                        # 判断库存(与原库存进行比对)
                        if sku_count > origin_stock:
                            # 抛出异常前需要进行回滚
                            transaction.savepoint_rollback(save_id)
                            raise serializers.ValidationError('库存不足')
                        # 模拟网络延迟
                        # import time
                        # time.sleep(5)

                        # 减少库存,增加销量 SKU
                        # sku.stock -= sku_count
                            # origin_stock = sku.stock
                            # new_stock = origin_stock - sku_count

                        # sku.sales += sku_count
                        # sku.save()

                        # 读取要更新的库存和销量
                        new_stock = origin_stock - sku_count
                        new_sales = origin_sales + sku_count

                        # 使用乐观锁更新库存:在调用update()去更新库存之前,使用filter()拿着原始的库存去查询记录是否存在
                        # 如果记录不存在,在调用update()时返回0
                        result = SKU.objects.filter(id=sku_id, stock=origin_stock).update(stock=new_stock, sales=new_sales)
                        # 工作经验:在进行判等时,为避免将==写成赋值=号,可将变量写在右边,当==写成=时,将报错
                        if 0 == result:
                            # 死循环的下单:当库存满足时,在下单时,库存没有同时被别人更改,下单成功,下单成功后,跳出死循环(break)
                            # 如果下单时库存被更改了,但是sku_count依然在被更改后的库存范围内,则继续下单(continue)
                            # 直到库存真正不满足条件时,才下单失败
                            continue

                        # 修改SPU销量
                        sku.goods.sales += sku_count
                        sku.goods.save()

                        # 保存订单商品信息 OrderGoods
                        OrderGoods.objects.create(
                            order=order,
                            sku=sku,
                            count=sku_count,
                            price=sku.price
                        )

                        # 累加计算订单总数量和总价
                        order.total_count += sku_count
                        order.total_amount += (sku_count * sku.price)

                        # 下单成功要跳出死循环
                        break

                # 最后加入邮费和保存订单信息
                order.total_amount += order.freight
                order.save()
            except serializers.ValidationError:
                # 这里不回滚的原因是前面已经针对序列化器异常有了回滚的动作
                raise
            except Exception:
                # 回滚
                transaction.savepoint_rollback(save_id)
                raise  # 自动的将捕获的异常抛出,不需要给异常起别名

            # 没有问题需要进行明显的提交,提交从保存点到当前状态的所有数据库事物的提交
            transaction.savepoint_commit(save_id)

            # 清除购物车中已结算的商品
            pl = redis_conn.pipeline()
            pl.hdel('cart_%s' % user.id, *redis_selected)
            pl.srem('selected_%s' % user.id, *redis_selected)
            pl.execute()

            # 响应结果
            return order
